package plp.functional2.expression;

import static java.util.Arrays.asList;
import static plp.expressions1.util.ToStringProvider.listToString;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import plp.expressions1.util.Tipo;
import plp.expressions2.expression.Expressao;
import plp.expressions2.expression.Id;
import plp.expressions2.expression.Valor;
import plp.expressions2.memory.AmbienteCompilacao;
import plp.expressions2.memory.AmbienteExecucao;
import plp.expressions2.memory.ContextoCompilacao;
import plp.expressions2.memory.ContextoExecucao;
import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.functional1.util.TipoFuncao;
import plp.functional1.util.TipoPolimorfico;
import plp.functional2.declaration.DefProcesso;
import plp.functional2.declaration.TipoProcesso;
import plp.functional2.primitive.Primitiva;
import plp.functional2.util.UtilsConcurrency;

public class ProcAplicacao extends Aplicacao implements Runnable{

	AmbienteExecucao ambiente;
	private int idProcess;

	public ProcAplicacao(int idProcess) {
		super();
		this.idProcess = idProcess;
	}

	public ProcAplicacao(Expressao f, Expressao... expressoes) {
		this(f, asList(expressoes));
	}

	public ProcAplicacao(Expressao f, List<? extends Expressao> expressoes) {
		super(f, expressoes);
	}

	public ProcAplicacao(AmbienteExecucao ambiente,Expressao f, List<? extends Expressao> expressoes) {
		super(f, expressoes);
		this.ambiente = ambiente;
		this.setIdProcess(UtilsConcurrency.getCurrentIdProcess());
	}

	/**
	 * Realiza a verificacao de tipos desta expressao.
	 * 
	 * @param amb
	 *            o ambiente de compila��o.
	 * @return <code>true</code> se os tipos da expressao sao validos;
	 *         <code>false</code> caso contrario.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador nao declarado no ambiente.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador declarado mais de uma vez no
	 *                mesmo bloco do ambiente.
	 */
	@Override
	public boolean checaTipo(AmbienteCompilacao ambiente)
	throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		Tipo tipo = getFuncType(ambiente);

		boolean result;

		TipoProcesso tipoProcesso = (TipoProcesso) tipo;
		result = tipoProcesso.checaTipo();

		return result;
	}

	private Tipo getFuncType(AmbienteCompilacao amb) {
		
		ContextoCompilacao ambiente = (ContextoCompilacao)amb;
		
		Tipo tipoFuncao = null;
		if (super.getFunc() instanceof Id) {
			tipoFuncao = ambiente.get((Id) super.getFunc());
		} else if (super.getFunc() instanceof ValorFuncao) {
			tipoFuncao = ((ValorFuncao) super.getFunc()).getTipo(ambiente);
		}

		if (tipoFuncao == null || tipoFuncao instanceof TipoPolimorfico) {
			ArrayList<Tipo> params = new ArrayList<Tipo>();
			for (Expressao valorReal : super.getArgsExpressao()) {
				params.add(valorReal.getTipo(ambiente));
			}
			tipoFuncao = new TipoFuncao(params, new TipoPolimorfico());
		}
		return tipoFuncao;
	}



	/**
	 * Retorna os tipos possiveis desta expressao.
	 * 
	 * @param amb
	 *            o ambiente de compila��o.
	 * @return os tipos possiveis desta expressao.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador nao declarado no ambiente.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador declarado mais de uma vez no
	 *                mesmo bloco do ambiente.
	 * @precondition this.checaTipo();
	 */
	public Tipo getTipo(AmbienteCompilacao ambiente)
	throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {

		Tipo tipo = getFuncType(ambiente);

		TipoFuncao tipoFuncao = (TipoFuncao) tipo;

		return tipoFuncao.getTipo(ambiente, super.getArgsExpressao());
	}

	private void includeValueBindings(AmbienteExecucao amb, Map<Id, Valor> mapIdValor) throws VariavelJaDeclaradaException {
		
		ContextoExecucao ambiente = (ContextoExecucao)amb;
		
		for (Map.Entry<Id, Valor> mapeamento : mapIdValor.entrySet()) {
			Id id = mapeamento.getKey();
			Valor valor = mapeamento.getValue();
			ambiente.map(id, valor);
		}
	}

	private Map<Id, Valor> resolveParametersBindings(AmbienteExecucao amb,	DefProcesso defProc) throws VariavelNaoDeclaradaException,
			VariavelJaDeclaradaException {
		
		ContextoExecucao ambiente = (ContextoExecucao)amb;
		
		List<Id> parametrosId = defProc.getListaId();
		List<? extends Expressao> expressoesValorReal = super.getArgsExpressao();

		Map<Id, Valor> mapIdValor = new HashMap<Id, Valor>();

		Iterator<? extends Expressao> iterExpressoesValor = expressoesValorReal
		.iterator();
		for (Id id : parametrosId) {
			Expressao exp = iterExpressoesValor.next();
			Valor valorReal = exp.avaliar(ambiente);
			mapIdValor.put(id, valorReal);
		}

		return mapIdValor;
	}

	/**
	 * Retorna uma representacao String desta expressao. Util para depuracao.
	 * 
	 * @return uma representacao String desta expressao.
	 */
	 @Override
	 public String toString() {
		return String.format("%s(%s)", super.getFunc(), listToString(super.getArgsExpressao(), ","));
	}

	public void run() {

		ValorProcesso processo = (ValorProcesso) super.getFunc().avaliar(this.ambiente);System.out.println("# Thread.run # processId: " + this.idProcess + " processo:" + processo);
		Map<Id, Valor> mapIdValor = resolveParametersBindings(this.ambiente, processo);
		((ContextoExecucao)this.ambiente).incrementa();
		includeValueBindings(this.ambiente, mapIdValor);
		Primitiva primitiva = processo.getPrimitiva();
		primitiva.executar(this.ambiente);

		((ContextoExecucao)this.ambiente).restaura();
		System.out.println("# Thread.runned # processId: "+this.idProcess );
		return;         
	}

	public int getIdProcess() {
		return idProcess;
	}

	public void setIdProcess(int idProcess) {
		this.idProcess = idProcess;
	}

}